// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class BybitSpotFeedUpdateParser {
 public:
  static bool
  ParseBook(BookBuilder* book, int64_t ts, const rapidjson::Document& document,
      bool light_bbo_move_xbp) {
    const std::string& type = "snapshot";
    if (type == "snapshot") {
      return BybitSpotFeedUpdateParser::ParseSnapshot(book, ts, document["data"]);
    } else if (type == "delta") {
      return BybitSpotFeedUpdateParser::ParseDelta(book, ts, document["data"], light_bbo_move_xbp);
    } else {
      return false;
    }
  }

  static bool
  ParseSnapshot(BookBuilder* book, int64_t ts, const rapidjson::Value& book_json) {
    auto tx = book->NewTransaction(ts);
    tx.Clear();
    UpdateBook(book_json, &tx);
    return true;
  }

  static bool
  ParseDelta(BookBuilder* book, int64_t ts, const rapidjson::Value& book_json,
      bool light_bbo_move_xbp) {
    auto tx = book->NewTransaction(ts);
    if (light_bbo_move_xbp) {
      tx.BboClear();
    }
    UpdateBook(book_json, &tx);

    return true;
  }

  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    trade->price = stod(trade_json["p"].GetString());
    trade->fill_qty = stod(trade_json["q"].GetString());
    trade->side = trade_json["m"].GetBool()
                    ? coin2::exchange::feed::TRADE_BUY_SIDE
                    : coin2::exchange::feed::TRADE_SELL_SIDE;
    trade->timestamp = stoll(trade_json["t"].GetString()) * 1'000'000LL;
  }

  static bool
  ParseBookTicker(BookBuilder* book, int64_t ts, const rapidjson::Document& doc) {
    auto tx = book->NewTransaction(ts);
    tx.Clear();
    const auto& data = doc["data"];
    tx.UpdateAsk(stod(data["ap"].GetString()), stod(data["aq"].GetString()));
    tx.UpdateBid(stod(data["bp"].GetString()), stod(data["bq"].GetString()));
    return true;
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15m", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"1h", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1d", fastfeed::proto::KLINE_INTERVAL_1DAY},
      {"1w", fastfeed::proto::KLINE_INTERVAL_1WEEK},
      {"1M", fastfeed::proto::KLINE_INTERVAL_1MON}
    };

    auto iter = interval_map.find(std::string(interval));
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    int64_t open_timestamp = stoll(msg["t"].GetString()) * 1'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(msg["o"].GetString());
    kline->high = stod(msg["h"].GetString());
    kline->low = stod(msg["l"].GetString());
    kline->close = stod(msg["c"].GetString());
    kline->volume = stod(msg["v"].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }

  private:

  static void
  UpdateBook(const rapidjson::Value& book_json, Transaction* tx) {
    for (const auto& ask_json : book_json["a"].GetArray()) {
      tx->UpdateAsk(stod(ask_json[0].GetString()), stod(ask_json[1].GetString()));
    }

    for (const auto& bid_json : book_json["b"].GetArray()) {
      tx->UpdateBid(stod(bid_json[0].GetString()), stod(bid_json[1].GetString()));
    }
  }
};
}  // namespace impl

using impl::BybitSpotFeedUpdateParser;
